package com.zkh.myutils.proxy;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.zkh.myutils.utils.UtilsException;
import com.zkh.myutils.utils.ArrayUtils;
import com.zkh.myutils.utils.Assert;
import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Type;

/**
 * 生成代理类并实例化的代理操作类
 * @author zkh
 */
public final class Proxy {
	
	/**
	 * 代理类，用来存放已经生成的代理类，不用再二次生成，提高速率
	 */
	private final static Map<String, Class<?>> proxyClassMap = new ConcurrentHashMap<>();
	
	/**
	 * 获取代理对象（对所有public修饰的方法作代理）
	 * @param classLoader 类加载器
	 * @param clazz 代理对象
	 * @param handler 自定义处理代理方法
	 */
	public static Object newProxyInstance(ClassLoader classLoader, Class<?> clazz, MethodHandler handler) throws Exception{
		return newProxyInstance(classLoader, clazz, ".+", handler, null);
	}
	
	/**
	 * 获取代理对象（对所有public修饰的方法作代理）
	 * @param classLoader 类加载器
	 * @param clazz 代理对象
	 * @param methodRegexp 代理方法名称正则
	 * @param handler 自定义处理代理方法
	 */
	public static Object newProxyInstance(ClassLoader classLoader, Class<?> clazz, String methodRegexp, MethodHandler handler) throws Exception{
		return newProxyInstance(classLoader, clazz, methodRegexp, handler, null);
	}
	
	/**
	 * 获取代理对象（对所有public修饰的方法作代理）
	 * @param classLoader 类加载器
	 * @param clazz 代理对象
	 * @param handler 自定义处理代理方法
	 * @param location 生成代理类class文件保存地址，用以调试
	 */
	public static Object newProxyInstance(ClassLoader classLoader, Class<?> clazz, MethodHandler handler, String location) throws Exception{
		return newProxyInstance(classLoader, clazz, ".+", handler, location);
	}
	
	/**
	 * 获取代理对象（对所有public修饰的方法作代理）
	 * @param classLoader 类加载器
	 * @param clazz 代理对象
	 * @param methodRegexp 代理方法名称正则（对抽象方法无效）
	 * @param handler 自定义处理代理方法
	 * @param location 生成代理类class文件保存地址，用以调试
	 */
	public static Object newProxyInstance(ClassLoader classLoader, Class<?> clazz, String methodRegexp, MethodHandler handler, String location) throws Exception {
		//被代理类和handler的名称
		String proxyClass = clazz.getName();	//handlerClass = handler.getClass().getName();
		//获取类型
		int mod = clazz.getModifiers();
		//final类
		if(Modifier.isFinal(mod)) {
			throw new UtilsException("final修饰的类无法生成代理对象");
		}
		//代理类不存在
		if(!proxyClassMap.containsKey(proxyClass)) {
			//生成代理
			execProxyCreate(classLoader, proxyClass, clazz, methodRegexp, location);
		}
		//实例化代理类并返回
		return proxyClassMap.get(proxyClass).getConstructor(MethodHandler.class).newInstance(handler);
	}
	
	/**
	 * 执行创建代理操作
	 * @param classLoader 类加载器
	 * @param proxyClass 被代理类全类名
	 * @param clazz 代理对象
	 * @param methodRegexp 代理方法名称正则（对抽象方法无效）
	 * @param location 生成代理类class文件保存地址，用以调试
	 * @return
	 * @throws Exception
	 */
	private synchronized static void execProxyCreate(ClassLoader classLoader, String proxyClass, Class<?> clazz, String methodRegexp, String location) throws Exception{
		//不存在，则创建
		if(!proxyClassMap.containsKey(proxyClass)) {
			//新代理类全类名称
			String proxyClassName = proxyClass + "$MyutilsProxy";
			//读取类方法
			ClassReader reader = new ClassReader(classLoader.getResourceAsStream(proxyClass.replace('.', '/') + ".class"));
			//生成类
			ClassWriter cw = new ClassWriter(0);
			//参数映射
			Map<String, List<String>> paramMap = parseParamName(clazz);
			//类型
			ClassType type = Assert.getIfElse(clazz.isInterface(), ClassType.INTERFACE, Assert.getIfElse(Modifier.isAbstract(clazz.getModifiers()), ClassType.ABSTRACT_CLASS, ClassType.GENERAL_CLASS));
			//使用自定义监听方法对指定类进行分析，然后写入到cw对象中实现生成类功能
			reader.accept(new ProxyClassPrinter(cw, proxyClassName, methodRegexp, type, paramMap), 0);
			//读取代理类
			byte[] proxyClassBytes = cw.toByteArray();
			//调试输出到文件
			if(StringUtils.isNotEmpty(location)) {
				write(proxyClassBytes, location, clazz.getSimpleName());
			}
			//将代理类加载到内存中供实例化
			Class<?> proxyClassClass = ClassLoaderUtils.defineClass(proxyClassName, proxyClassBytes,  classLoader);
			//保存参数
			proxyClassClass.getField("argNamesMap").set(null, paramMap);
			//保存到最终容器
			proxyClassMap.put(proxyClass, proxyClassClass);
		}
	}
	
	/**
	 * 解析所有方法的参数名称
	 * @param clazz
	 * @return
	 */
	private static Map<String, List<String>> parseParamName(Class<?> clazz) {
		//参数映射
		Map<String, List<String>> paramMap = new HashMap<String, List<String>>();
		//遍历所有方法
		Method[] methods = clazz.getMethods();
		//是否有参数名称标识
		boolean hasVarName = true;
		//遍历
		for(int i=0,len=methods.length; i<len; i++) {
			//没有参数名，则退出
			if(!hasVarName) {
				break;
			}
			//当前方法
			Method method = methods[i];
			//获取参数
			Parameter[] params = method.getParameters();
			//有参数
			if(params.length>0 && (hasVarName = hasVarName || params[0].isNamePresent())) {
				//参数名称容器
				List<String> paramList = new ArrayList<String>();
				//遍历
				for(int j=0,size=params.length; j<size; j++) {
					paramList.add(params[j].getName());
				}
				/** 参数类型 **/
				//前缀
				StringBuffer sb = new StringBuffer("autograph：");
				//遍历类型
				ArrayUtils.forEach(method.getParameterTypes(), (t, j)->{
					sb.append(Type.getDescriptor(t));
				});
				//保存映射
				paramMap.put(method.getName() + "$$" + Utils.md5Short(sb.toString()), paramList);
			}
		}
		//返回
		return paramMap;
	}
	
	/**
	 * 调试输出类
	 * @param proxyClassBytes 生成的class的二进制流
	 * @param location 文件生成目录
	 */
	public static void write(byte[] proxyClassBytes, String location, String className) {
		try {
			//文件
			File file = new File(location, className + "$MyutilsProxy.class");
			//输出路
			OutputStream os = new FileOutputStream(file);
			//写入内容
			os.write(proxyClassBytes);
			//关闭流
			os.close();
		}catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
}
